﻿using System;
using System.Diagnostics;
using PT.Common;
using PT.Timing.Timing;

namespace PT.Timing {
    public class Metronome {
        /// <summary> Tempo of the metronome. Expressed as 
        /// the number of quarternotes in 1 minute. </summary>
        public int Tempo { get; set; }

        /// <summary> Time signature for a bar. </summary>
        public Rational TimeSignature { get; set; }

        readonly Timer t;

        readonly TimingState s;

        internal TimingState State {
            get { return s; }
        }

        readonly ClickList clicks;

        public ClickList Clicks {
            get { return clicks; }
        }

        public bool IsRunning { get; private set; }

        /// <summary> Duration of a bar (in ticks). </summary>
        internal long BarDuration {
            get {
                if (Tempo <= 0) {
                    return 0;
                }

                // Can move to Tempo.set method
                long quarterNoteDurationT = 60 * Stopwatch.Frequency / Tempo;

                // Can move to TimeSignature.set method
                Rational converter = Rational.Divide(TimeSignature, Rational.Quarter);
                converter.Reduce();

                return quarterNoteDurationT * converter.Numerator / converter.Denominator;
            }
        }

        public event EventHandler<ClickEventArgs> Click;

        public Metronome() {
            Tempo = 60;
            TimeSignature = new Rational(4, 4);

            clicks = ClickList.BuildDefaultClickList();
            s = new TimingState();

            t = new Timer {Mode = TimerMode.Periodic, Resolution = 1, Period = 1};
            t.Tick += TimerOnTick;
        }

        public void Start() {
            s.Time = s.LastCalcTime = Stopwatch.GetTimestamp();
            foreach (Click c in clicks) {
                c.LastClickTime = s.Time - 1;
            }

            t.Start();
            IsRunning = true;
        }

        public void Stop() {
            t.Stop();
            IsRunning = false;
        }

        void TimerOnTick(object sender, EventArgs e) {
            s.Time = Stopwatch.GetTimestamp();

            // Calculate clicks and raise events as appropriate
            Click raisableClick = null;
            ClickResult raisableClickResult = null;
            for (int i = clicks.Count - 1; 0 <= i; --i) {
                Click click = clicks[i];
                ClickResult res = click.CalcClick(this);

                if (res.Equals(ClickResult.NoClick)) {
                    continue;
                }

                click.LastClickTime = res.ActualTime;
                raisableClick = click;
                raisableClickResult = res;
            }

            s.LastCalcTime = s.Time;

            if (raisableClick != null) {
                OnClick(raisableClick, raisableClickResult, s.Time);
            }
        }

        void OnClick(Click data, ClickResult result, long eventTimestamp) {
            EventHandler<ClickEventArgs> subscribers = Click;

            if (subscribers != null) {
                var args = new ClickEventArgs(data, result.ActualTime, eventTimestamp);
                subscribers(this, args);
            }
        }

        internal class TimingState {
            public long Time { get; set; }
            public long LastCalcTime { get; set; }
            public long LastBarStartTime { get; set; }
        }
    }
}